package cn.nkpro.elcube.co.quartz;

import cn.nkpro.elcube.annotation.NkNote;
import cn.nkpro.elcube.basic.NkProperties;
import cn.nkpro.elcube.co.quartz.model.NkJobDetail;
import cn.nkpro.elcube.utils.ClassUtils;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Component
public class NkSchedulerManager implements ApplicationRunner {

    @Autowired
    private Scheduler scheduler;
    @Autowired
    private NkProperties nkProperties;

    public List<NkJobDetail> jobs() throws SchedulerException {
        return scheduler.getJobKeys(GroupMatcher.anyGroup())
                    .stream()
                    .map(key->{
                        NkJobDetail job = new NkJobDetail();
                        job.setKey(key);

                        try {
                            Class<?> clazz = Class.forName(key.toString());
                            NkNote nkNote = clazz.getAnnotation(NkNote.class);
                            job.setDescription(nkNote!=null?nkNote.value():key.getName());
                        } catch (ClassNotFoundException ignored) {
                        }

                        return job;
                    }).collect(Collectors.toList());
    }

    public void addTrigger(String name, String group, String triggerName, String triggerCron) throws SchedulerException {

        JobKey jobKey = JobKey.jobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);

        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerName, jobKey.toString())
                .withSchedule(CronScheduleBuilder.cronSchedule(triggerCron))
                .forJob(jobDetail)
                .startNow()
                .build();
        scheduler.scheduleJob(trigger);
    }

    public void removeTrigger(String name, String group) throws SchedulerException {
        TriggerKey key = new TriggerKey(name, group);
        scheduler.unscheduleJob(key);
    }

    @Override
    public void run(ApplicationArguments args) {

        try{

            Set<Class<? extends NkQuartzJob>> types = new HashSet<>(load("cn.nkpro.elcube"));

            String[] packages = nkProperties.getScheduledBasePackages();
            for(String pk : packages){
                types.addAll(load(pk));
            }

            Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyJobGroup());

            for(Class<? extends NkQuartzJob> type : types){

                NkNote nkNote = type.getAnnotation(NkNote.class);

                JobKey jobKey = JobKey.jobKey(type.getSimpleName(), type.getPackage().getName());

                if(!jobKeys.contains(jobKey)){
                    JobDetail jobDetail = JobBuilder.newJob(type)
                            .withIdentity(type.getSimpleName(), type.getPackage().getName())
                            .withDescription(nkNote!=null?nkNote.value():type.getSimpleName())
                            .storeDurably()
                            .build();
                    scheduler.addJob(jobDetail,false);

                    NkScheduled[] schedules = type.getAnnotationsByType(NkScheduled.class);
                    if(schedules!=null){
                        for(NkScheduled scheduled : schedules){
                            CronTrigger trigger = TriggerBuilder.newTrigger()
                                    .withIdentity(scheduled.name(), type.getName())
                                    .withSchedule(CronScheduleBuilder.cronSchedule(scheduled.cron()))
                                    .forJob(jobDetail)
                                    .startNow()
                                    .build();
                            scheduler.scheduleJob(trigger);
                        }
                    }
                }

                jobKeys.remove(jobKey);
            }

            for(JobKey jobKey : jobKeys){
                scheduler.deleteJob(jobKey);
            }
        }catch (Exception e){
            e.printStackTrace();
            System.exit(0);
        }
    }

    @SuppressWarnings("all")
    private Set<Class<? extends NkQuartzJob>> load(String prefix) throws IOException {

        Set<Class<? extends NkQuartzJob>> set = new HashSet<>();

        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        MetadataReaderFactory readerfactory = new CachingMetadataReaderFactory(resourcePatternResolver);

        Resource[] resources = resourcePatternResolver.getResources(String.format("classpath*:/%s/**/*.class", packageToPath(prefix)));

        for(Resource resource : resources){
            MetadataReader reader = readerfactory.getMetadataReader(resource);
            //扫描到的class
            String classname = reader.getClassMetadata().getClassName();

            try{
                Class<?> clazz = Class.forName(classname);
                if(!Modifier.isAbstract(clazz.getModifiers()) && ClassUtils.hasInterface(clazz,NkQuartzJob.class)){
                    set.add((Class<? extends NkQuartzJob>) clazz);
                }
            }catch (Throwable ignored){

            }
        }

        return set;
    }

    private String packageToPath(String packageName){
        packageName = packageName.replaceAll("[.]","/");
        if(packageName.startsWith("/")){
            packageName = packageName.substring(1);
        }
        if(packageName.endsWith("/")){
            packageName = packageName.substring(0,packageName.length()-1);
        }
        return packageName;
    }
}
